using System;
using System.Collections.Generic;
using System.Text;
using System.Web.Security;
using System.Collections.Specialized;
using FreeLancer.HRB.Domains;
using System.Configuration.Provider;
using FreeLancer.HRB.Services;

namespace FreeLancer.HRB.Core.Security
{
  /// <summary>
  /// Generic Db Membership Provider
  /// </summary>
  public class DbMembershipProvider : MembershipProvider
  {
    private string _applicationName;
    private MembershipPasswordFormat _passwordFormat;
    private UsersService _userService = new UsersService();

    /// <summary>
    /// Initializes the provider.
    /// </summary>
    /// <param name="name">Configuration name.</param>
    /// <param name="config">Configuration settings.</param>
    public override void Initialize(string name, NameValueCollection config)
    {
      if (config == null)
      {
        throw new ArgumentNullException("config");
      }

      if (String.IsNullOrEmpty(name))
      {
        name = "DbMembershipProvider";
      }

      if (Type.GetType("Mono.Runtime") != null)
      {
        // Mono dies with a "Unrecognized attribute: description" if a description is part of the config.
        if (!string.IsNullOrEmpty(config["description"]))
        {
          config.Remove("description");
        }
      }
      else
      {
        if (string.IsNullOrEmpty(config["description"]))
        {
          config.Remove("description");
          config.Add("description", "Generic Database Membership Provider");
        }
      }

      base.Initialize(name, config);

      // Application Name
      if (config["applicationName"] == null)
      {
        config["applicationName"] = "HRB";
      }
      _applicationName = config["applicationName"];
      config.Remove("applicationName");

      //Password Format
      if (config["passwordFormat"] == null)
      {
        config["passwordFormat"] = "Hashed";
        _passwordFormat = MembershipPasswordFormat.Hashed;
      }
      else if (String.Compare(config["passwordFormat"], "clear", true) == 0)
      {
        _passwordFormat = MembershipPasswordFormat.Clear;
      }
      else
      {
        _passwordFormat = MembershipPasswordFormat.Hashed;
      }
      config.Remove("passwordFormat");

      // Throw an exception if unrecognized attributes remain
      if (config.Count > 0)
      {
        string attr = config.GetKey(0);
        if (!String.IsNullOrEmpty(attr))
          throw new ProviderException("Unrecognized attribute: " + attr);
      }
    }

    /// <summary>
    /// Add new user to database.
    /// </summary>
    /// <param name="username">Username.</param>
    /// <param name="password">Password.</param>
    /// <param name="email">Email.</param>
    /// <param name="passwordQuestion">Question.</param>
    /// <param name="passwordAnswer">Answer.</param>
    /// <param name="isApproved">Is approved</param>
    /// <param name="providerUserKey">Provider user key.</param>
    /// <param name="status">Status</param>
    /// <returns>Membership User</returns>
    public override MembershipUser CreateUser(string username, string password, string email,
                                              string passwordQuestion, string passwordAnswer, bool isApproved,
                                              object providerUserKey, out MembershipCreateStatus status)
    {
      MembershipUser membershipUser;

      using (ConnectionScope.CreateTransaction())
      {
        Users user = new Users();
        user.UserName = username;
        if (_passwordFormat == MembershipPasswordFormat.Hashed)
          user.Password = Utils.HashPassword(password);
        else
          user.Password = password;
        user.EmailAddress = email.ToLower();
        user.LastLoginTime = DateTime.Now;

        membershipUser = GetMembershipUser(username, email, DateTime.Now);

        _userService.Save(user);

        if (ConnectionScope.Complete())
          status = MembershipCreateStatus.Success;
        else
          status = MembershipCreateStatus.ProviderError;
      }

      return membershipUser;
    }

    /// <summary>
    /// Returns the application name as set in the web.config
    /// otherwise returns BlogEngine.  Set will throw an error.
    /// </summary>
    public override string ApplicationName
    {
      get { return _applicationName; }
      set { throw new NotImplementedException(); }
    }

    /// <summary>
    /// Change the password if the old password matches what is stored.
    /// </summary>
    /// <param name="username">Username.</param>
    /// <param name="oldPassword">Old password.</param>
    /// <param name="newPassword">New password.</param>
    /// <returns>Whether password has changed successfully.</returns>
    public override bool ChangePassword(string username, string oldPassword, string newPassword)
    {
      bool oldPasswordCorrect = false;
      bool success = false;

      Users user = GetUserByUserName(username);

      if (user != null)
      {
        string actualPassword = user.Password;
        if (actualPassword == "")
        {
          // This is a special case used for resetting.
          if (oldPassword.ToLower() == "admin")
            oldPasswordCorrect = true;
        }
        else
        {
          if (_passwordFormat == MembershipPasswordFormat.Hashed)
          {
            if (actualPassword == Utils.HashPassword(oldPassword))
              oldPasswordCorrect = true;
          }
          else if (actualPassword == oldPassword)
            oldPasswordCorrect = true;

        }

        if (oldPasswordCorrect)
        {
          if (_passwordFormat == MembershipPasswordFormat.Hashed)
            user.Password = Utils.HashPassword(newPassword);
          else
            user.Password = newPassword;

          using (ConnectionScope.CreateTransaction())
          {
            _userService.Save(user);
            success = ConnectionScope.Complete();
          }
        }
      }

      return success;
    }

    /// <summary>
    /// Not implemented.
    /// </summary>
    /// <param name="username">Username.</param>
    /// <param name="password">Password.</param>
    /// <param name="newPasswordQuestion">New question.</param>
    /// <param name="newPasswordAnswer">New answer.</param>
    /// <returns>Not implemented</returns>
    public override bool ChangePasswordQuestionAndAnswer(string username, string password, string newPasswordQuestion, string newPasswordAnswer)
    {
      throw new Exception("The method or operation is not implemented.");
    }

    /// <summary>
    /// Delete user from database.
    /// </summary>
    /// <param name="username">Username.</param>
    /// <param name="deleteAllRelatedData">Delete all related data.</param>
    /// <returns></returns>
    public override bool DeleteUser(string username, bool deleteAllRelatedData)
    {
      Users user = GetUserByUserName(username);
      bool success = false;

      if (user != null)
      {
        using (ConnectionScope.CreateTransaction())
        {
          _userService.Delete(user);
          success = ConnectionScope.Complete();
        }
      }

      return success;
    }

    /// <summary>
    /// Hardcoded to false.
    /// </summary>
    public override bool EnablePasswordReset
    {
      get { return false; }
    }

    /// <summary>
    /// Can password be retrieved via email?
    /// </summary>
    public override bool EnablePasswordRetrieval
    {
      get { return false; }
    }

    /// <summary>
    /// Not implemented.
    /// </summary>
    /// <param name="emailToMatch">Email to match.</param>
    /// <param name="pageIndex">Page index.</param>
    /// <param name="pageSize">Page size.</param>
    /// <param name="totalRecords">Total records.</param>
    /// <returns>Not implemented.</returns>
    public override MembershipUserCollection FindUsersByEmail(string emailToMatch, int pageIndex, int pageSize, out int totalRecords)
    {
      throw new Exception("The method or operation is not implemented.");
    }

    /// <summary>
    /// Not implemented.
    /// </summary>
    /// <param name="usernameToMatch">Username to match.</param>
    /// <param name="pageIndex">Page index.</param>
    /// <param name="pageSize">Page size.</param>
    /// <param name="totalRecords">Total records.</param>
    /// <returns>Not implemented.</returns>
    public override MembershipUserCollection FindUsersByName(string usernameToMatch, int pageIndex, int pageSize, out int totalRecords)
    {
      throw new Exception("The method or operation is not implemented.");
    }

    /// <summary>
    /// Return all users in MembershipUserCollection.
    /// </summary>
    /// <param name="pageIndex">Page index.</param>
    /// <param name="pageSize">Page size</param>
    /// <param name="totalRecords">Total records</param>
    /// <returns>Membership Users</returns>
    public override MembershipUserCollection GetAllUsers(int pageIndex, int pageSize, out int totalRecords)
    {
      MembershipUserCollection users = new MembershipUserCollection();
      TList<Users> userDomains = _userService.GetPaged(pageIndex, pageSize, out totalRecords);
      foreach (Users userDomain in userDomains)
      {
        MembershipUser user = GetMembershipUser(userDomain.UserName, 
                                              userDomain.EmailAddress, 
                                              userDomain.LastLoginTime ?? DateTime.MinValue);

        users.Add(user);
      }

      return users;
    }

    /// <summary>
    /// Not implemented.
    /// </summary>
    /// <returns>Not implemented</returns>
    public override int GetNumberOfUsersOnline()
    {
      throw new Exception("The method or operation is not implemented.");
    }

    /// <summary>
    /// Not implemented.
    /// </summary>
    /// <param name="username">Username.</param>
    /// <param name="answer">Answer.</param>
    /// <returns>Not implemented.</returns>
    public override string GetPassword(string username, string answer)
    {
      throw new Exception("The method or operation is not implemented.");
    }

    /// <summary>
    /// Get User by Username.
    /// </summary>
    /// <param name="username">Username.</param>
    /// <param name="userIsOnline">User is online.</param>
    /// <returns>Membership User.</returns>
    public override MembershipUser GetUser(string username, bool userIsOnline)
    {
      MembershipUser user = null;
      Users userDomain = GetUserByUserName(username);

      if (userDomain != null)
      {
        user = GetMembershipUser(username, userDomain.Password, 
          userDomain.LastLoginTime ?? DateTime.MinValue);
      }

      return user;
    }

    /// <summary>
    /// Get User by providerUserKey.
    /// </summary>
    /// <param name="providerUserKey">User key</param>
    /// <param name="userIsOnline">User is online.</param>
    /// <returns>Membership User.</returns>
    public override MembershipUser GetUser(object providerUserKey, bool userIsOnline)
    {
      return GetUser(providerUserKey.ToString(), userIsOnline);
    }

    /// <summary>
    /// Retrieve UserName for given e-mail.
    /// </summary>
    /// <param name="email">E-mail address.</param>
    /// <returns>Username</returns>
    public override string GetUserNameByEmail(string email)
    {
      if (email == null)
        throw new ArgumentNullException("email");
      
      string userName = null;
      int total = 0;
      Users user = null;
      TList<Users> users = _userService.GetPaged("EmailAddress='" + email + "'", string.Empty, 0, 1, out total);

      if (users.Count > 0)
      {
        user = users[0];
        userName = user.EmailAddress;
      }

      return userName;
    }

    /// <summary>
    /// Hardcoded to 5.
    /// </summary>
    public override int MaxInvalidPasswordAttempts
    {
      get { return 5; }
    }

    /// <summary>
    /// Hardcoded to 0
    /// </summary>
    public override int MinRequiredNonAlphanumericCharacters
    {
      get { return 0; }
    }

    /// <summary>
    /// Hardcoded to 6
    /// </summary>
    public override int MinRequiredPasswordLength
    {
      get { return 6; }
    }

    /// <summary>
    /// Not implemented
    /// </summary>
    public override int PasswordAttemptWindow
    {
      get { throw new Exception("The method or operation is not implemented."); }
    }

    /// <summary>
    /// Password format (Clear or Hashed)
    /// </summary>
    public override MembershipPasswordFormat PasswordFormat
    {
      get { return _passwordFormat; }
    }

    /// <summary>
    /// Not Implemented.
    /// </summary>
    public override string PasswordStrengthRegularExpression
    {
      get { throw new Exception("The method or operation is not implemented."); }
    }

    /// <summary>
    /// Hardcoded to false.
    /// </summary>
    public override bool RequiresQuestionAndAnswer
    {
      get { return false; }
    }

    /// <summary>
    /// Hardcoded to false.
    /// </summary>
    public override bool RequiresUniqueEmail
    {
      get { return false; }
    }

    /// <summary>
    /// Not implemented.
    /// </summary>
    /// <param name="username">Username.</param>
    /// <param name="answer">Answer.</param>
    /// <returns>Not implemented</returns>
    public override string ResetPassword(string username, string answer)
    {
      throw new Exception("The method or operation is not implemented.");
    }

    /// <summary>
    /// Not implemented.
    /// </summary>
    /// <param name="userName">Username</param>
    /// <returns>Not implemented.</returns>
    public override bool UnlockUser(string userName)
    {
      throw new Exception("The method or operation is not implemented.");
    }

    /// <summary>
    /// Update User Data (not password).
    /// </summary>
    /// <param name="user">User.</param>
    public override void UpdateUser(MembershipUser user)
    {
      Users userDomain = GetUserByUserName(user.UserName);

      if (userDomain != null)
      {
        userDomain.EmailAddress = user.Email;

        _userService.Save(userDomain);
      }
    }

    /// <summary>
    /// Check username and password.
    /// </summary>
    /// <param name="username">Username.</param>
    /// <param name="password">Password.</param>
    /// <returns>Whether user is valid</returns>
    public override bool ValidateUser(string username, string password)
    {
      bool validated = false;
      Users user = GetUserByUserName(username);

      if (user != null)
      {
        string storedPwd = user.Password;

        if (storedPwd == string.Empty)
        {
          // This is a special case used for resetting.
          if (password.ToLower() == "admin")
            validated = true;
        }
        else
        {
          if (_passwordFormat == MembershipPasswordFormat.Hashed)
          {
            if (storedPwd == Utils.HashPassword(password))
              validated = true;
          }
          else if (storedPwd == password)
            validated = true;
        }
      }

      return validated;
    }

    private MembershipUser GetMembershipUser(string userName, string email, DateTime lastLogin)
    {
      MembershipUser user = new MembershipUser(
                              Name,                       // Provider name
                              userName,                   // Username
                              userName,                   // providerUserKey
                              email,                      // Email
                              String.Empty,               // passwordQuestion
                              String.Empty,               // Comment
                              true,                       // isApproved
                              false,                      // isLockedOut
                              DateTime.Now,               // creationDate
                              lastLogin,                  // lastLoginDate
                              DateTime.Now,               // lastActivityDate
                              DateTime.Now,               // lastPasswordChangedDate
                              new DateTime(1980, 1, 1)    // lastLockoutDate
                          );
      return user;
    }

    private Users GetUserByUserName(string userName)
    {
      int total = 0;
      Users user = null;
      TList<Users> users = _userService.GetPaged("UserName='" + userName + "'", string.Empty, 0, 1, out total);

      if (users.Count > 0)
      {
        user = users[0];
      }

      return user;
    }
  }
}
